﻿using System.Linq;
using System;
using System.Web.Security;
using Web;
using System.Security.Cryptography;
using Web.Models;
using System.Collections.Generic;

namespace Web.BL
{
    public class Service
    {
        private DataModelContainer db = new DataModelContainer();

        /// <summary>This method gets a User from the database</summary>
        /// <param name=”name”>Username</param>
        private User GetUser(string name)
        {
            User u = db.User.SingleOrDefault(r => r.name.Equals(name));
            return u;
        }

        /*public User GetUser(string recipient)
        {
            User user = db.User.SingleOrDefault(u => u.name.Equals(recipient));

            return user;
        }*/

        /// <summary>This method gets a User from the database</summary>
        /// <param name=”id”>the user id</param>
        private User GetUser(int id)
        {
            User user = db.User.Single(u => u.id == id);
            return user;
        }

        /// <summary>This method adds a group to the database</summary>
        /// <param name=”groupname”>The name of the group</param>
        /// <param name=”username”>The name of the user that created the group</param>
        public bool CreateGroup(string groupname, string username)
        {
            if(groupname != null && username != null){
                User user = GetUser(username);
                Crew group = GetGroup(groupname);
                if (user != null && group == null)
                {
                    db.Crew.Add(new Crew() { name = groupname, Userowner = user });
                    db.SaveChanges();
                }

            }
            return false;
        }

        /// <summary>This method gets a representation of a user</summary>
        /// <param name=”name”>the username</param>
        public UserRepresentation GetUserRepresentation(string name)
        {
            UserRepresentation user = null;
            User u = GetUser(name);

            if (u != null)
            {
                DateTime daysAgo = DateTime.Now.AddDays(-30);
                int nr = db.Login.Where(r => r.User.id == u.id && r.logintime >= daysAgo).Count();
                int totallogins = db.Login.Where(r => r.User.id == u.id).Count();
                DateTime latestLogin = DateTime.Now;

                if (totallogins > 1)
                {
                    latestLogin = db.Login.Where(l => l.User.id == u.id).OrderByDescending(l => l.logintime).Skip(1).Max(l => l.logintime);
                }
                user = new UserRepresentation(u.id, u.name, u.email, nr, latestLogin);
            }

            return user;

        }

        /// <summary>This method adds a User to the database</summary>
        /// <param name=”username”>the username</param>
        /// <param name=”password”>the password</param>
        /// <param name=”email”>the email address</param>
        public UserRepresentation CreateUser(string username, string password, string email)
        {
            UserRepresentation user = null;
            username = username.Trim();
            if (username.Length == 0) { throw new InvalidUsernameException(); }
            
            if (db.User.Where(u1 => u1.name.Equals(username)).Count() > 0)
            {
                throw new DuplicateUsernameException();
            }
            else if (db.User.Where(u1 => u1.email.Equals(email)).Count() > 0)
            {
                throw new DuplicateEmailException();
            }
            else
            {
                string salt = Guid.NewGuid().ToString();
                string passwordhash = CreatePasswordHash(password, salt);
    
                User u = new User()
                { 
                name = username,
                email = email,
                salt = salt,
                password = passwordhash
                };

                db.User.Add(u);
                u.Login.Add(new Login() { logintime = DateTime.Now, User = u });

                db.SaveChanges();
 
                user = new UserRepresentation(u.id, username, email, 1, DateTime.Now);

            }
            return user;
        }

        /// <summary>This method validates a User</summary>
        /// <param name=”username”>the username</param>
        /// <param name=”password”>the password</param>
        public bool ValidateUser(string username, string password)
        {
            User dbuser = GetUser(username);
            if (dbuser != null && dbuser.password.Equals(Service.CreatePasswordHash(password, dbuser.salt)))
            {
                dbuser.Login.Add(new Login() { logintime = DateTime.Now, User = dbuser });
                db.SaveChanges();
                return true;
            }
            return false;
        }

        /// <summary>This method creates a sha1 hash</summary>
        /// <param name=”pwd”>the password</param>
        /// <param name=”salt”>the salt</param>
        private static string CreatePasswordHash(string pwd, string salt)
        {
            string saltAndPwd = String.Concat(pwd, salt);
            string hashedPwd = FormsAuthentication.HashPasswordForStoringInConfigFile(saltAndPwd, "sha1");
            return hashedPwd;
        }

        /// <summary>This method gets a group from the database</summary>
        /// <param name=”id”>the group id</param>
        public Crew GetGroup(int id)
        {
            Crew crew = db.Crew.Single(c => c.id == id);
            return crew;
        }

        /// <summary>This method gets a group from the database</summary>
        /// <param name=”groupname”>the name assigned to the group</param>
        public Crew GetGroup(string groupname)
        {
            try
            {
                return db.Crew.Single(c => c.name.Equals(groupname));
            }
            catch
            {
                return null;
            }
        }

        /// <summary>This method gets all groups a user have joined</summary>
        /// <param name=”groupname”>the name assigned to the group</param>
        public List<Crew> GetUserGroups(string username)
        {
            try{
                return db.Crew.Where(c => c.members.Any(m => m.name.Equals(username))).ToList();
            }
            catch{
                return new List<Crew>();
            }
        }

        /// <summary>This method joins a user to a group </summary>
        /// <param name=”group_Id”>the group id</param>
        /// <param name=”user_Id”>the user id</param>
        public Boolean JoinGroup(int user_Id, int group_Id)
        {
            try
            {
                User u = GetUser(user_Id);
                Crew c = GetGroup(group_Id);
                u.groups.Add(c);
                db.SaveChanges();
                return true;
            }
            catch
            {
            }
            return false;
        }

        /// <summary>This method returns all groups that have a name containing the keyword</summary>
        /// <param name=”q”>the search string</param>
        public List<Crew> GroupSearch(string q)
        {
            try
            {
                return db.Crew.Where(g => g.name.Contains(q) || String.IsNullOrEmpty(q)).ToList();
            }
            catch
            {
                return new List<Crew>();
            }
        }


        /// <summary>This method lets a user leave a group he/she previously joined</summary>
        /// <param name=”id”>the group id</param>
        /// <param name=”username”>the username</param>
        public bool LeaveGroup(int id, string username)
        {
            try
            {
                User user = GetUser(username);
                Crew crew = GetGroup(id);
                user.groups.Remove(crew);
                db.SaveChanges();
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Get all the messages a specific user have received
        /// </summary>
        /// <param name="userrep">user object containing user information</param>
        /// <returns>List<MessageRepresentation></returns>
        public List<MessageRepresentation> GetMessageRepresentationsRecievedBy(UserRepresentation userrep)
        {
            int id = userrep.GetId();
            var messages = db.Message.Where(m => m.receiver == id && m.deleted == false && m.archived == false).OrderByDescending(m => m.timesent);
            List<MessageRepresentation> messagelist = new List<MessageRepresentation>();
            foreach (Message m in messages)
            {
                messagelist.Add(new MessageRepresentation(m.title,m.text,m.id,m.Usersender.name,m.Userreceiver.name,m.timesent,m.archived,m.deleted,m.opened));
            }
            return messagelist;
        }

        /// <summary>
        /// Get all the messages a specific user have received between two dates
        /// </summary>
        /// <param name="userId">id of the user</param>
        /// <param name="from">from date</param>
        /// <param name="to">to date</param>
        /// <returns> List<MessageRepresentation> </returns>
        public List<MessageRepresentation> GetMessageRepresentationsRecievedBy(int userId, DateTime from, DateTime to)
        {
            var messages = db.Message.Where(m => m.receiver == userId && m.deleted == false && m.archived == false && m.timesent >= from && m.timesent <= to).OrderByDescending(m => m.timesent);
            List<MessageRepresentation> messagelist = new List<MessageRepresentation>();
            foreach (Message m in messages)
            {
                messagelist.Add(new MessageRepresentation(m.title, m.text, m.id, m.Usersender.name, m.Userreceiver.name, m.timesent, m.archived, m.deleted, m.opened));
            }
            return messagelist;
        }

        /// <summary>
        /// Get all the messages sent by a specific user
        /// </summary>
        /// <param name="userrep">UserRepresentation object containing user information</param>
        /// <returns> List<MessageRepresentation> </returns>
        public List<MessageRepresentation> GetMessageRepresentationsSentBy(UserRepresentation userrep)
        {
            int id = userrep.GetId();
            var messages = db.Message.Where(m => m.sender == id && m.deleted == false && m.archived == false).OrderByDescending(m => m.timesent);
            List<MessageRepresentation> messagelist = new List<MessageRepresentation>();
            foreach (Message m in messages)
            {
                messagelist.Add(new MessageRepresentation(m.title,m.text,m.id,m.Usersender.name,m.Userreceiver.name,m.timesent,m.archived,m.deleted,m.opened));
            }
            return messagelist;
        }

        /// <summary>
        /// Get all the messages which a specific user have archivated
        /// </summary>
        /// <param name="userrep">UserRepresentation object containing user information</param>
        /// <returns> List<Message> </returns>
        public List<Message> GetArchivedMessagesForUser(UserRepresentation userrep)
        {
            int id = userrep.GetId();
            List<Message> messages = new List<Message>(db.Message.Where(m => m.receiver == id && m.deleted == false && m.archived == true).OrderByDescending(m => m.timesent));


            return messages;
        }

        /// <summary>
        /// Get all the messages a specific user have received
        /// </summary>
        /// <param name="userrep">UserRepresentation object containing user information</param>
        /// <returns> List<Message> </returns>
        public List<Message> GetRecievedMessagesForUser(UserRepresentation userrep)
        {
            int id = userrep.GetId();
            List<Message> messages = new List<Message>(db.Message.Where(m => m.receiver == id && m.deleted == false && m.archived == false).OrderByDescending(m => m.timesent));


            return messages;
        }

        /// <summary>
        /// Get all the messages a specific user have sent
        /// </summary>
        /// <param name="userrep">UserRepresentation object containing user information</param>
        /// <returns> List<Message> </returns>
        public List<Message> GetSentMessagesForUser(UserRepresentation userrep)
        {
            int id = userrep.GetId();
            List<Message> messages = new List<Message>(db.Message.Where(m => m.sender == id).OrderByDescending(m => m.timesent));


            return messages;
        }

        /// <summary>
        /// This function archivates a specific message
        /// </summary>
        /// <param name="id">id of the message</param>
        /// <returns>true if successful</returns>
        public bool ArchivateMessage(int id)
        {
            try
            {
                db.Message.Single(m => m.id == id).archived = true;
                db.SaveChanges();
                return true;
            }
            catch{
            }
            return false;
        }

        /// <summary>
        /// Get a specific message
        /// </summary>
        /// <param name="id">int id of the message</param>
        /// <returns>Message object</returns>
        public Message getMessageById(int id)
        {
            return db.Message.SingleOrDefault(m => m.id == id);
        }

        /// <summary>
        /// Deletes a message (sets the delete flag = true)
        /// </summary>
        /// <param name="id">id of the message</param>
        /// <returns>true if successful</returns>
        public bool DeleteMessage(int id)
        {
            Message message = db.Message.SingleOrDefault(m => m.id == id);
            if (message != null)
            {
                message.deleted = true;
                db.SaveChanges();
                return true;
            }
            else{
                return false;
            }
        }
        /*public void AddMessage(Message message)
        {
            db.Message.Add(message);
        }*/

        /// <summary>
        /// Get all the messages a specific user have received that neither archived not deleted
        /// </summary>
        /// <param name="id">the user id</param>
        /// <param name="from">from date</param>
        /// <param name="to">to date</param>
        /// <param name="q">search string</param>
        /// <returns> List<MessageRepresentation> </returns>
        public List<MessageRepresentation> GetMessageRepresentationsRecievedBy(int id, DateTime from, DateTime to, string q)
        {
            var messages = db.Message.Where(m => m.receiver == id && m.deleted == false && m.archived == false && m.timesent >= from && m.timesent <= to && (m.title.Contains(q) || m.text.Contains(q) || m.Usersender.name.Contains(q))  ).OrderByDescending(m => m.timesent);
            List<MessageRepresentation> messagelist = new List<MessageRepresentation>();
            foreach (Message m in messages)
            {
                messagelist.Add(new MessageRepresentation(m.title, m.text, m.id, m.Usersender.name, m.Userreceiver.name, m.timesent, m.archived, m.deleted, m.opened));
            }
            return messagelist;

        }
    }
}